package cn.cuilan.secondkill.service;

import cn.cuilan.secondkill.redis.JedisTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 秒杀核心服务，redis简单队列实现
 */
@Service
public class SecondKillService {

    // 抢购成功后存入的队列
    private final static String SECOND_KILL_KEY = "sk:queue";

    // redis分布式锁
    private final static String REDIS_LOCK = "redis:lock";

    // 已抢购成功的key，防止同一个人多次抢到
    private final static String SUCCESS_ID_KEY = "success:";

    // 队列最大数量，即商品库存数量
    private final static int QUEUE_SIZE = 10;

    private JedisTemplate jedisTemplate;


    // 怎样防止用户抢购的商品大于库存？
    // 1.商品已抢到的计数器，当计数器等于最大库存数时，就不能再放出任何商品了
    // 因为消费者线程可能正在进行消费，会导致队列中的数量不是10，不能简单的只用队列大小来判断，
    // 2.或者用已存在的用户id数量来判断已放出多少商品，也是一种解决办法
    // 3.也可以用锁中的value来记录，每次放出商品以后自增1，当达到10的时候不能再放出商品
    private AtomicInteger count = new AtomicInteger();

    @Autowired
    public SecondKillService(JedisTemplate jedisTemplate) {
        this.jedisTemplate = jedisTemplate;
    }

    /**
     * 将商品放入队列
     * 使用 redis 的 list 类型来做一个简单队列
     * 加入队列：rpush 放入队列尾
     * 取出一个：lpop 从队列头部取值
     *
     * @param userId 模拟用户id
     * @return 返回成功与否的状态
     */
    public String push(String userId) {
        return jedisTemplate.execute(jedis -> {
            // 1.多线程高并发访问时，首先获取队列的长度，已达最大值直接返回。
            Long queueSize = jedis.llen(SECOND_KILL_KEY);
            if (queueSize >= QUEUE_SIZE) {
                // 中断当前线程
                Thread.currentThread().interrupt();
                return "奖品已被抢空!";
            }

            // 2.设置redis分布式锁，使用 setnx 命令，setnx 如果key已存在，则不做任何操作
            if (jedis.setnx(REDIS_LOCK, String.valueOf(Thread.currentThread().getId())) != 0L) {

                // 3.设置过期时间为2秒，防止线程阻塞导致不会释放锁
                jedis.expire(REDIS_LOCK, 2);

                // 4.再次获取队列的长度
                queueSize = jedis.llen(SECOND_KILL_KEY);
                if (queueSize >= QUEUE_SIZE) {
                    // 当前线程中断
                    Thread.currentThread().interrupt();
                    return "奖品已被抢空!";
                }

                // 5.判断当前线程是否已经抢到商品
                if (jedis.get(SUCCESS_ID_KEY + userId) != null) {
                    return "你已经抢到了";
                }
                // 如果没有抢到，则记录已抢到的用户id，并设置过期时间，可以很长，一直到秒杀活动结束
                jedis.set(SUCCESS_ID_KEY + userId, userId);
                jedis.expire(SUCCESS_ID_KEY + userId, 100);

                // 6.将已抢到商品的用户id加入队列，供消费线程消费队列，入库等操作
                Long rpush = jedis.rpush(SECOND_KILL_KEY, userId);

                // 7.释放redis分布式锁
                jedis.del(REDIS_LOCK);
                return "抢购成功!";
            } else {
                // 如果没有获取redis锁，再次排队
                return "人太多了，请排队";
            }
        });
    }

}
